package org.javanum.number;

/**
 * <p>An immutable implementation of the
 * {@link org.javanum.number.PolarComplexField} interface. This class
 * uses primitive {@code double} elements to represent its
 * magnitude and angle. To reduce the number of autoboxings and
 * auto-unboxings,this class directly accesses its
 * memory whenever possible.</p>
 *
 * <p>Since this class is immutable, it is also thread-safe</p>
 *
 * Author: Scott Fines
 * Date: November 26, 2009
 * 
 */
public class PolarComplex extends
						AbstractPolarComplex<Real, PolarComplex, Double> {

    /**
     * The zero element as a singleton. This element is returned by the
     * method {@link #additiveIdentity()}
     */
	@SuppressWarnings({"WeakerAccess"})
    public static final PolarComplex ZERO = new PolarComplex(0,0){
        @Override
        public Double modulus() {
            return 0d;
        }

        @Override
        public Real magnitude() {
            return Real.ZERO;
        }

        @Override
        public PolarComplex negate() {
            return PolarComplex.ZERO;
        }

        @Override
        public Complex cartesianForm() {
            return Complex.ZERO;
        }

        @Override
        public PolarComplex additiveInverse() {
            return PolarComplex.ZERO;
        }
    };

    public static final PolarComplex COMPLEX_INFINITY =
                    new PolarComplex(Double.POSITIVE_INFINITY,0){
                        @Override
                        public Complex cartesianForm() {
                            return Complex.COMPLEX_INFINITY;
                        }
                    };

    public static final PolarComplex NaN
                    = new PolarComplex(Double.NaN,Double.NaN);

    /**
     * The multiplicative identity as a singleton. This element is
     * returned by the method {@link #multiplicativeIdentity()}
     */
	@SuppressWarnings({"WeakerAccess"})
    public static final PolarComplex ONE =new PolarComplex(1,0);

    public static final PolarComplex I = new PolarComplex(1,Math.PI/2);

    private static final double twoPi = Math.PI*2;
    private static final double negTwoPi = -twoPi;
	
	public final double magnitude;
	public final double angle;
/*----------------------------------------------------------*/
    /*Constructors*/

    private PolarComplex(double magnitude, double angle){
		this.magnitude=magnitude;
        this.angle=getAngle(angle);
	}

    private static double getAngle(double angle) {
        double ang = angle;

        if(ang<negTwoPi){
            ang = ang%negTwoPi;


        }else if(ang>twoPi){
            ang = ang%twoPi;


        }

        if(ang<-Math.PI)
                ang +=twoPi;
        else if(ang>Math.PI)
                ang-=twoPi;
        return ang;
    }

    public static PolarComplex valueOf(double magnitude, double angle){
        if(magnitude<0)
            throw new IllegalArgumentException("Magnitude cannot be negative");
        return new PolarComplex(magnitude,angle);
    }

    public static PolarComplex valueOf(Real magnitude, Real angle){
        return valueOf(magnitude.value,angle.value);
    }

/*----------------------------------------------------------*/
    /*Getters*/

    @Override
	public Real angle() {
		return new Real(angle);
	}

    @Override
    public Real magnitude() {
        return new Real(magnitude);
    }

/*----------------------------------------------------------*/
    /*Addition methods*/

    @Override
    public PolarComplex add(PolarComplex summand) {
        return this.cartesianForm().
                    add(summand.cartesianForm()).
                    polarForm();
    }

    @Override
    public PolarComplex additiveIdentity() {
        return PolarComplex.ZERO;
    }

    @Override
    public PolarComplex additiveInverse() {
        return new PolarComplex(magnitude,angle+Math.PI);
    }

    @Override
    public PolarComplex add(Real value) {
        return this.cartesianForm().add(value).polarForm();
    }

/*----------------------------------------------------------*/
    /*Subtraction methods*/

    @Override
    public PolarComplex subtract(PolarComplex value) {
        return this.cartesianForm().
                subtract(value.cartesianForm()).
                polarForm();
    }

    @Override
    public PolarComplex subtract(Real value) {
        return this.cartesianForm().subtract(value).polarForm();
//        return subtract(value.asPolarComplex());
    }

/*----------------------------------------------------------*/
    /*Multiplication methods*/

    @Override
	public PolarComplex multiply(PolarComplex value) {
		return
           new PolarComplex(magnitude*value.magnitude,angle+value.angle);
	}

    @Override
    public PolarComplex multiply(Real value) {
        if(Real.isNaN(value))
            return PolarComplex.NaN;
        double newMag = Math.abs(value.value)*magnitude;
        double relAngle = value.value<0?-Math.PI:0;

        double newAngle = angle+relAngle;

        return new PolarComplex(newMag,newAngle);
    }

    @Override
    public PolarComplex multiplicativeIdentity() {
        return PolarComplex.ONE;
    }

    @Override
    public PolarComplex multiplicativeInverse() {
        return new PolarComplex(1/magnitude,-angle);
    }

/*----------------------------------------------------------*/
    /*Division methods*/

    @Override
    public PolarComplex divide(PolarComplex divisor) {
        if(Double.compare(divisor.magnitude,0)==0)
            throw new ArithmeticException("Divide By Zero");
        return new PolarComplex(this.magnitude/divisor.magnitude,
                this.angle-divisor.angle);
    }

    @Override
    public PolarComplex divide(Real value) {
        if(Real.isNaN(value))
            return PolarComplex.NaN;
        if(Double.compare(value.value,0)==0)
            throw new ArithmeticException("Divide By Zero");
        double newMag = magnitude/Math.abs(value.value);
        double relAng = value.value<0?-Math.PI:0d;
        double newAngle = angle-relAng;
        return new PolarComplex(newMag,newAngle);
    }

/*----------------------------------------------------------*/
    /*Conjugation, Modulus, and Negation methods*/

    @Override
	public PolarComplex conjugate() {
		return new PolarComplex(magnitude,-angle);
	}

    @Override
    public Double modulus() {
        return magnitude;
    }

    @Override
    public PolarComplex negate(){
        return new PolarComplex(magnitude,angle-Math.PI);
    }

/*----------------------------------------------------------*/
    /*Misc. methods*/

    @Override
    public String toString() {
        return magnitude+"e^("+angle+"i)";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof PolarComplex)) return false;

        PolarComplex that = (PolarComplex) o;

        return Double.compare(that.angle, angle) == 0 &&
                Double.compare(that.magnitude, magnitude) == 0;

    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        temp = magnitude != +0.0d ? Double.doubleToLongBits(magnitude) : 0L;
        result = (int) (temp ^ (temp >>> 32));
        temp = angle != +0.0d ? Double.doubleToLongBits(angle) : 0L;
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @SuppressWarnings("unchecked") //Complex is an implementation
								   //of CartesianComplexField
	@Override
	public Complex cartesianForm() {
        double realAbs = magnitude*Math.cos(angle);
        double imagAbs = magnitude*Math.sin(angle);

		return Complex.valueOf(realAbs,imagAbs);
	}

    public static boolean isInfinite(PolarComplex z){
        return Double.isInfinite(z.magnitude);
    }

    public static boolean isNaN(PolarComplex z){
        return Double.isNaN(z.magnitude);
    }

}
